// test
// 递归求和 1-100
function add(num1, num2) {
    var num = num1 + num2
    num2++
    if (num2 > 100) {
        return num
    } else {
        return add(num, num2)
    }
}

// console.log(add(1, 2));

// 防抖 在事件被触发n秒后执行 如果这段时间内重复被触发 则会重新计算时间
function debounce(func, times) {
    let timeout
    return function () {
        if (timeout) clearTimeout(timeout)
        timeout = setTimeout(() => {
            func.apply(this, arguments)
        }, times)
    }
}

// 节流 节流就像是稀释 在某段时间内 时间被触发是无效的 单位时间内 该事件只能被触发一次
function throttle(func, times) {
    let run = false
    return function () {
        if (run) return
        run = true
        setTimeout(() => {
            func.apply(this, arguments)
            run = false
        }, times)
    }
}

// 浅拷贝 Object.assign()

// 深拷贝 JSON.parse(JSON.stringify(object))
function deepClone(source) {
    if (typeof source != 'object') return
    let target = Array.isArray(source) ? [] : {}
    for (let key in source) {

        if (typeof source[key] == 'object' && source[key] != null) {
            target[key] = deepClone(source[key])
        } else {
            target[key] = source[key]
        }

    }
    return target
}

// 检测对象是否循环引用 过滤循环引用对象 - 同时也是深拷贝
function circular(source) {
    const cache = new WeakMap();
    return (function reCheck (value, path) {
        // 1. 判断对象的类型
        if (
            typeof value == 'object' &&
            value !== null &&
            !(value instanceof String) &&
            !(value instanceof Symbol) && 
            !(value instanceof Date) && 
            !(value instanceof RegExp)
        ) {
            if (cache.has(value)) {
                return path
            }

            cache.set(value, path)

            let temp
            if (Array.isArray(value)) {
                temp = []
                for (let i in value) {
                    temp[i] = reCheck(value[i], `${path}${i}`)
                }
            } else {
                temp = {}
                for (let key in value) {
                    temp[key] = reCheck(value[key], path)
                }
            }

            return temp
        } else {
            return value
        }
    })(source, '$');
}

// const testObject = { name: 'mal', age: 18, t: { name2: 'sss', age: 10 } }
// let a = {name: 'mal'}
// let b = {parent: a, age: 18}
// a.child = b

// const cir = circular(testObject)
// testObject.t.name2 = 'xxxx'
// cir.age = 19
// console.log(111, testObject);
// console.log(222, cir)

// const test2 = deepClone(testObject)


/*
    html渲染过程
    1. 浏览器解析html文件，请求js/image/css 文件 生成dom树
    2. 浏览器解析css文件 生成cssom 规则树
    3. 结合dom树和cssom 规则树会生成一个render树 render树才是浏览器真正渲染的树
       即 根据渲染树计算每一个节点信息  浏览将页面渲染到前端页面
    4. 这几个步骤并不是依次顺序执行的，因为css和js都会造成dom树、cssom树的重新渲染

    eg：重绘：某个元素的背景颜色或者字体颜色改变，引起重绘，不影响周围元素或内部布局的属性，浏览器只是会重绘
        回流：某个元素的尺寸发生了变化，影响到周围的布局，则会引起浏览器重新计算渲染树，重新渲染

*/

/*
    浏览从url输入到拿到结果经历了什么？
        总体的步骤：
            1. DNS解析 DNS将域名解析成IP地址
                浏览器先查看该域名是否存在缓存 如果没有缓存则通过DNS服务器将域名解析成ip地址
            2. TCP的三次握手与服务器建立连接
                客户端：告诉服务器 我要发送请求了
                服务端：我准备好了 你发送请求吧
                客户端：我马上就发送请求 你准备接受
            3. 发送http请求
                请求头
                    告诉服务器有关客户端的有用信息 比如 主机名、content-type、cookie
                请求行
                    url、请求方法、协议版本
                请求体
                    要请求的主体信息 
            4. 服务器相应http 报文
                相应行
                    状态码
                响应头
                响应主体
                    返回的数据
            5. 浏览器进行页面渲染
                1. html文件生成dom树
                2. css文件生成css规则树
                3. 结合css规则树和dom树生成render 渲染树
                4. 计算每一个节点信息 渲染到前端页面
            6. TCP的四次挥手 断开连接
                客户端：请求报文发送完毕 准备关闭
                服务器：请求报文接受完毕 你也准备关闭
                服务器端：响应报文发送完毕 你准备关闭
                客户端：响应报文接受完毕 我准备关闭了 你也准备关闭吧
*/

/*
    new操作符都做了什么事？
        1.let obj = {}  ->  创建一个空对象
        2.obj.__proto__ = constructFn.prototype  ->  这个对象的原型__proto__指向构造函数的原型对象prototype
        3.constructFn.call(obj)  ->  将构造函数的this绑定为这个对象
*/


/*
    原型链
        每个对象都有自己的原型对象 prototype
        构造函数中为什么要将方法和属性放入自己的原型对象中？
            我们在通过构造函数实例化对象时，构造函数的属性和方法会被重新创建出来，如果只是简单数据类型，只需要赋值即可，但是如果是函数，那么他是要重新开辟空间的，实例化一个对象就开辟一些空间，这样造成了内存浪费；如果我们将所有实例所共有的属性和方法放置在一个对象中，在实例化的时候只需要指向这个对象进行引用即可，这样就出现了原型prototype
        原型对象至少有两个属性：construct 和 __proto__ ；construct指向的是实例本身，__proto__指向的是父构造函数
        __proto__提供的是一条查找线路，__proto__指向的是该对象的原型；我们在调用实例对象的方法时，先从实例本身的属性和方法中查找，当找不到时，就会通过__proto__到父构造函数的原型对象中找，直到找到空对象为止
*/

/* 
    js如何实现继承？
        1. 通过call 将父构造函数的this替换成子构造函数
        2. 通过原型的方法 父构造函数实例化对象的prototype

        继承的几种方式：
            1. 原型链继承：
                子类原型是父类的实例
                    优点: 原型方法实现了共享
                    缺点: 实例的引用属性的改动对所有的实例都生效。实例化时无法给父类构造函数传递参数。
            2. 盗用构造函数继承 
                子类构造函数的作用域内执行父类构造函数 SuperType.call(this, options)
                    优点: 解决引用属性的问题
                    缺点: 只能继承父类构造函数的属性,原型上的方法没有继承
            3. 组合继承 
                结合原型链继承和盗用构造函数继承,子类构造函数的作用域内执行父类构造函数,同时子类原型是父类的实例
                    优点: 即解决了引用属性的问题,原型链上的属性方法也能够继承
                    缺点: 执行了两次父构造函数(1. 子类构造函数内执行父类构造函数 2. 子类原型=new 父类), 这导致父类的构造函数内的属性在子类中有两个,一个在实例属性中,一个在原型对象上,虽然实例属性会覆盖原型对象上的属性,但这也造成了空间浪费
            4. 寄生式组合继承
*/
function Father(name) {
    this.name = name
}

function Son(name, age) {
    Father.call(this, name)
    this.age = age
}

Son.prototype = new Father('mal')
Son.prototype.constructor = Son

// 算法----
// 数组去重
function unique(arr) {
    let temp = [arr[0]]
    arr.map(item => {
        if (temp.indexOf(item) < 0) {
            temp.push(item)
        }
    })
    return temp
}

function unique2(array){
    const result = [];
    const tempMap = new Map();
    array.map(item => {
        if(!tempMap.has(item)) {
            result.push(item);
            tempMap.set(item, item);
        }
    })
    return result;
}

function unique3(array) {
    // 数组扁平化
    const a = array.toString().split(',').map(item => Number(item))
    return [...new Set(a)]
}

// 排序
function my_sort(arr) {
    let temp = arr[0]
    let tempArr = []
    arr.map((item, i) => {
        if (i >= 1) {
            if (item >= temp) tempArr.push(item)
            else tempArr.unshift(item)
        }
    })
    return tempArr
}

// 计算各项重复次数
function my_repeat(arr) {
    const temp = {}
    // 找到一个重复的就去除一个并且计数 将当前得到的值存放至对象中
    arr.map((item, i) => {
        let counter = 0
        arr.map((option, j) => {
            if (j > i && item == option) {
                counter++
                arr.splice(j, 1)
            }
        })
        temp[item] = counter
    })
    return temp
}

/*
    事件循环
        栈：只能在表尾进行插入和删除的线性表；故而又称为 后进先出
        队列：只能在表尾进行插入，表头进行删除的线性表；故而又称为 先进先出
        JavaScript调用栈：
            javascript是一门单线语言，他有一个主线程和调用栈（执行栈），所有的任务都会放入调用栈中，等待主线程的执行；
            既然是栈，那他就遵循后进先出的规则，函数在被执行的时候，被添加到栈的顶部，执行完毕后再被移除栈，直到栈被清空；

        事件循环是什么？
            js从上到下解析其中的方法，将同步任务的函数放入到执行栈中，将异步任务挂起，拿到结果后，会将异步任务的回调结果按照顺序放入事件队列中；
            执行栈中等待主线程的执行，执行完成一个同步任务后，将这个任务从栈顶移除，直到清空执行栈；当清空执行栈后，主线程会检查事件队列中是否存在任务，如果存在，就将事件队列中的第一个任务推到执行栈中，在这个过程中如果遇到异步任务，还是将这个异步任务挂起，拿到回调结果后放入到事件队列中；
            这个过程循环往复，就叫做事件循环

*/

/*
    vue实现响应式原理：
        vue实例在被创建出来时，会实例化一个Observer 对象，这个对象做了一件事：将每个data中的属性做了一层代理-> Object.defineProporty(object, key) ，这样每个数据中的属性都含有一个getter和setter方法；
        当你使用data中的某个属性时，会触发他的getter方法，这时候vue会去调用dep对像，进行依赖收集；
        dep对象做了两件事，1. 依赖收集； -> 添加观察者对象到数组中； 2. 通知依赖；-> notify方法，遍历所有的收集到的watcher对象，通知他进行更新数据
        watcher对象中做数据的更新
*/

/*
    new 操作符做了什么事？
        1. 创建一个空对象 let obj = {}
        2. 使用 __proto__ 指向构造函数的原型对象 obj.__proto__ = Xxx.prototype
        3. 在 obj 作用域内执行构造函数，this 指向该对象 Xxx.call(obj, 'xxx')
        4, 返回对象 retrun obj
*/  

